/**
 * CS543 HW4 Joseph Meyer
 */
package edu.src.hardware;

import java.util.HashMap;
import java.util.Map;

import edu.src.address.Address;
import edu.src.paging.Page;
import edu.src.paging.PageResult;

/**
 * The TLB. A singleton.
 * <p>
 * To avoid the overhead of translating every virtual address by looking up<br>
	the PDE and PTE , processors use translation look-aside buffer ( TLB ) hardware,<br>
	which contains an associative memory cache for mapping virtual pages to<br>
	PTE s. The TLB is part of the memory-management unit ( MMU ) within each<br>
	processor. The MMU needs to “walk” (navigate the data structures of) the page<br>
	table in memory only when a needed translation is missing from the TLB .<br>
 *  - pg 849 <br>
 *  
 * @author meyer
 */
public class TranslationLookasideBuffer {
	
	/** A map of logical Address to physical Address */
	private Map<Address, Page> tlb;
	private static TranslationLookasideBuffer instance;
	private int hit = 0;
	private int miss = 0;
	private PhysicalMemory pMemory;
	
	/**
	 * Private constructor
	 */
	private TranslationLookasideBuffer() {
		tlb = new HashMap<Address, Page>(16);
		pMemory = PhysicalMemory.getInstance();
	}
	
	/**
	 * @return TranslationLookasideBuffer - the Singleton instance
	 */
	public static TranslationLookasideBuffer getInstance() {
		if (instance == null) {
			instance = new TranslationLookasideBuffer();
		}
		return instance;
	}
	
	/**
	 * Package private TLB reset method.
	 */
	public void emptyTLB() {
		tlb.clear();
	}
	
	/**
	 * Check the TLB if it contains the address to 
	 * resolve to physical memory. 
	 * <p>
	 * If it does, the TLB allows us to skip the entire page table process
	 * and pull the byte value directory from PhysicalMemory.
	 * <p>
	 * @param a - the Virtual Address to resolve
	 * @return Page - null if not in the TLB
	 */
	public PageResult checkTLB(Address a) {
		if (tlb.keySet().contains(a)) {
			//our TLB contains the Virtual Address, we know
			//the result is loaded into physical memory and where it is.
			hit++;
			Page p = tlb.get(a);
			int physicalMemoryAddress = p.getPhysicalMemoryAddress();
			byte value = pMemory.getByteFromPhysicalMemory(p);
			return new PageResult(a.getPageNumber(), a.getOffset(), value, physicalMemoryAddress, a.getAddress());
		} 
		miss++;
		return null;
		
	}
	
	/**
	 * Add a page to the TLB (if there is room).
	 * Remove a page from the TLB if there is no room.
	 * Replacement performed via LRU
	 * @param address - Address - key to the TLB.
	 * @param pageToAddToTLB - the value from the physical address.
	 */
	public void addOrReplaceTLB(Address address) {
		
		if (tlb.size() < 16) {
			//room to spare
			Page p = new Page();
			p.setTimestamp(System.currentTimeMillis());
			tlb.put(address, p);
		} else {
			//need to remove the Least Recently Used (LRU) entry
			long oldestTimestamp = Long.MAX_VALUE;
			Address keyToRemove = null;
			for (Map.Entry<Address, Page> entrySet : tlb.entrySet()) {
				long timestamp = entrySet.getValue().getTimestamp();
				if (timestamp < oldestTimestamp) {
					//recognizing this as the oldest entry
					keyToRemove = entrySet.getKey();
				}
			}
			
			if (keyToRemove != null) {
				tlb.remove(keyToRemove);
			}
		}
	}

	/**
	 * How many times accesses resulted in a 'hit'
	 * @return the hit. 
	 */
	public int getHit() {
		return hit;
	}

	/**
	 * How many times the TLB search gave a 'miss'
	 * @return the miss
	 */
	public int getMiss() {
		return miss;
	}
	
	/**
	 * The percent of accesses to the TLB that resulted in hits
	 * @return double - %
	 */
	public double getHitRate() {
		int total = hit + miss;
		double result = ((double)hit) / ((double)total);
		return result * 100;
	}
	
	/**
	 * @return int - number of items in the TLB
	 */
	public int getCurrentTLBsize() {
		return tlb.size();
	}
}
